// Tierless
// Copyright © 2010 Alphora
// This file is licensed under a modified BSD-license (see tierless_license.txt).

using System;
using System.Collections.Generic;

using Tierless.Framework.Data;
using Tierless.Framework.Entities;
using Tierless.Framework.EntityTypes;

namespace Tierless.Framework.Constraints
{
	/// <summary>
	/// Represents the possible locations for constraint enforcement.
	/// </summary>
	[Flags]
	public enum ConstraintLocation
	{
		/// <summary>
		/// The constraint can be evaluated on the server side only.
		/// </summary>
		Server = 1,

		/// <summary>
		/// The constraint can be evaluated on the client side only.
		/// </summary>
		Client = 2,

		/// <summary>
		/// The constraint can be evaluated on the client and server side.
		/// </summary>
		Both = 3
	}

	/// <summary>
	/// Represents the possible transitions for constraint enforcement.
	/// </summary>
	[Flags]
	public enum ConstraintTransition
	{
		/// <summary>
		/// The constraint should be enforced on any insert transition.
		/// </summary>
		Insert = 1,

		/// <summary>
		/// The constraint should be enforced on any update transition.
		/// </summary>
		Update = 2,

		/// <summary>
		/// The constraint should be enforced on any delete transition.
		/// </summary>
		Delete = 4,

		/// <summary>
		/// The constraint should be enforced on insert or update.
		/// </summary>
		InsertUpdate = 3,

		/// <summary>
		/// The constraint should be enforced on update or delete.
		/// </summary>
		UpdateDelete = 6,

		/// <summary>
		/// The constraint should be enforced on insert, update, and delete.
		/// </summary>
		All = 7
	}

	/// <summary>
	/// Provides an abstract base class for the constraint evaluation engine.
	/// </summary>
	public abstract class ConstraintEngine
	{
		/// <summary>
		/// Initializes a new instance of the ConstraintEngine class.
		/// </summary>
		/// <param name="location">The location where the constraint engine will run.</param>
		/// <param name="dataService">Provides a service to access entities during constraint evaluation.</param>
		/// <exception cref="ArgumentException">The location parameter is ConstraintLocation.Both. An engine can only be instantiated as a server or client side engine, not both.</exception>
		public ConstraintEngine(ConstraintLocation location, IEntityAccessor dataService)
		{
			if (location == ConstraintLocation.Both)
				throw new ArgumentException("Constraint service location must be server or client, not both");

			Location = location;
			_dataService = dataService;
		}

		/// <summary>
		/// Gets the constraint location for this engine.
		/// </summary>
		public ConstraintLocation Location { get; private set; }

		private IEntityAccessor _dataService;

		/// <summary>
		/// Retrieves the list of constraints to be evaluated for the given entity type.
		/// </summary>
		/// <param name="entityTypeName">The name of the entity type.</param>
		/// <returns>A list of <see cref="EntityConstraint"/> instances representing the constraints to be evaluated for the entity type.</returns>
		protected abstract IList<EntityConstraint> GetConstraintsForEntityType(string entityTypeName);

		/// <summary>
		/// Evaluates the constraints for the given list of entity changes.
		/// </summary>
		/// <param name="changes">The changes to be validated.</param>
		/// <param name="entities">The entities in context.</param>
		/// <returns>True if the validation is successful, false if there were any error violations.</returns>
		public bool Execute(List<EntityChange> changes, EntitySets entities)
		{
			var result = true;

			foreach (var change in changes)
			{
				change.Entity.Violations.Clear();
				foreach (var constraint in GetConstraintsForEntityType(change.Entity.EntityTypeName))
				{
					if ((constraint.Location & Location) > 0)
					{
						var violation = constraint.Validate(_dataService, change, entities);
						if (violation != null)
						{
							change.Entity.Violations.Add(violation);
							result = false;
						}
					}
				}
			}

			return result;
		}
	}
}